/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// modified from original source see README at the top level of this project

package io.requery.ohos.database;

import net.sqlcipher.CharArrayBuffer;
import net.sqlcipher.CursorIndexOutOfBoundsException;
import ohos.aafwk.ability.DataAbilityHelper;
import ohos.aafwk.ability.IDataAbilityObserver;
import ohos.data.rdb.DataObservable;
import ohos.data.rdb.DataObserver;
import ohos.data.resultset.ResultSet;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.PacMap;
import ohos.utils.net.Uri;

import java.lang.ref.WeakReference;
import java.util.List;

/**
 * This is an abstract cursor class that handles a lot of the common code
 * that all cursors need to deal with and is provided for convenience reasons.
 */
public abstract class AbstractCursor implements ResultSet {

    private static final String TAG = "Cursor";
    private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00207, TAG);

    protected int mPos;

    protected boolean mClosed;

    //@Deprecated // deprecated in AOSP but still used for non-deprecated methods
    protected DataAbilityHelper mContentResolver;

    private Uri mNotifyUri;

    private List<Uri> mNotifyUriList;

    private final Object mSelfObserverLock = new Object();
    private IDataAbilityObserver mSelfObserver;
    private boolean mSelfObserverRegistered;
    private final DataObservable mDataSetObservable = new DataObservable();

    private PacMap mExtras = PacMap.EMPTY_PAC_MAP;

    @Override
    abstract public int getRowCount();

    @Override
    abstract public String[] getAllColumnNames();

    @Override
    abstract public String getString(int column);
    @Override
    abstract public short getShort(int column);
    @Override
    abstract public int getInt(int column);
    @Override
    abstract public long getLong(int column);
    @Override
    abstract public float getFloat(int column);
    @Override
    abstract public double getDouble(int column);
    @Override
    abstract public boolean isColumnNull(int column);

    @Override
    public abstract ColumnType getColumnTypeForIndex(int column);

    @Override
    public byte[] getBlob(int column) {
        throw new UnsupportedOperationException("getBlob is not supported");
    }

    @Override
    public int getColumnCount() {
        return getAllColumnNames().length;
    }

    //@Override
    public void deactivate() {
        onDeactivateOrClose();
    }

    /** @hide */
    /**
     * onDeactivateOrClose
     */
    protected void onDeactivateOrClose() {
        if (mSelfObserver != null) {
            mContentResolver.unregisterObserver(mNotifyUri,mSelfObserver);
            mSelfObserverRegistered = false;
        }
        mDataSetObservable.notifyObservers();
    }

    //@Override
    public boolean requery() {
        if (mSelfObserver != null && !mSelfObserverRegistered) {
            mContentResolver.registerObserver(mNotifyUri, mSelfObserver);
            mSelfObserverRegistered = true;
        }
        mDataSetObservable.notifyObservers();
        return true;
    }

    @Override
    public boolean isClosed() {
        return mClosed;
    }

    @Override
    public void close() {
        mClosed = true;
        //mContentObservable.unregisterAll();
        onDeactivateOrClose();
    }

    //@Override
    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
        // Default implementation, uses getString
        String result = getString(columnIndex);
        if (result != null) {
            char[] data = buffer.data;
            if (data == null || data.length < result.length()) {
                buffer.data = result.toCharArray();
            } else {
                result.getChars(0, result.length(), data, 0);
            }
            buffer.sizeCopied = result.length();
        } else {
            buffer.sizeCopied = 0;
        }
    }

    public AbstractCursor() {
        mPos = -1;
    }

    @Override
    public final int getRowIndex() {
        return mPos;
    }

    @Override
    public final boolean goToRow(int position) {
        // Make sure position isn't past the end of the cursor
        final int count = getRowCount();
        if (position >= count) {
            mPos = count;
            return false;
        }

        // Make sure position isn't before the beginning of the cursor
        if (position < 0) {
            mPos = -1;
            return false;
        }

        // Check for no-op moves, and skip the rest of the work for them
        if (position == mPos) {
            return true;
        }

        boolean result = onMove(mPos, position);
        if (!result) {
            mPos = -1;
        } else {
            mPos = position;
        }

        return result;
    }

    /**
     * This function is called every time the cursor is successfully scrolled
     * to a new position, giving the subclass a chance to update any state it
     * may have.  If it returns false the move function will also do so and the
     * cursor will scroll to the beforeFirst position.
     * <p>
     * This function should be called by methods such as {@link #moveToPosition(int)},
     * so it will typically not be called from outside of the cursor class itself.
     * </p>
     *
     * @param oldPosition The position that we're moving from.
     * @param newPosition The position that we're moving to.
     * @return True if the move is successful, false otherwise.
     */
    public abstract boolean onMove(int oldPosition, int newPosition);

    @Override
    public final boolean goTo(int offset) {
        return goToRow(mPos + offset);
    }

    @Override
    public final boolean goToFirstRow() {
        return goToRow(0);
    }

    @Override
    public final boolean goToLastRow() {
        return goToRow(getRowCount() - 1);
    }

    @Override
    public final boolean goToNextRow() {
        return goToRow(mPos + 1);
    }

    @Override
    public final boolean goToPreviousRow() {
        return goToRow(mPos - 1);
    }

    @Override
    public final boolean isAtFirstRow() {
        return mPos == 0 && getRowCount() != 0;
    }

    @Override
    public final boolean isAtLastRow() {
        int cnt = getRowCount();
        return mPos == (cnt - 1) && cnt != 0;
    }

    @Override
    public final boolean isStarted() {
        return getRowCount() == 0 || mPos == -1;
    }

    @Override
    public final boolean isEnded() {
        return getRowCount() == 0 || mPos == getRowCount();
    }

    @Override
    public int getColumnIndexForName(String columnName) {
        // Hack according to bug 903852
        final int periodIndex = columnName.lastIndexOf('.');
        if (periodIndex != -1) {
            Exception e = new Exception();
            HiLog.error(label, "requesting column name with table name -- " + columnName, e);
            columnName = columnName.substring(periodIndex + 1);
        }

        String columnNames[] = getAllColumnNames();
        int length = columnNames.length;
        for (int i = 0; i < length; i++) {
            if (columnNames[i].equalsIgnoreCase(columnName)) {
                return i;
            }
        }
        return -1;
    }

    //@Override
    public int getColumnIndexOrThrow(String columnName) {
        final int index = getColumnIndexForName(columnName);
        if (index < 0) {
            throw new IllegalArgumentException("column '" + columnName + "' does not exist");
        }
        return index;
    }

    @Override
    public String getColumnNameForIndex(int columnIndex) {
        return getAllColumnNames()[columnIndex];
    }

    //@Override
    public void  registerObserver(DataObserver observer) {
        // mContentObservable.registerObserver(observer);
    }

    //@Override
    public void unregisterObserver(DataObserver observer) {
        // cursor will unregister all observers when it close
        if (!mClosed) {
            // mContentObservable.unregisterObserver(observer);
        }
    }

    /* @Override
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }*/ //Todo

    /**
     * Subclasses must call this method when they finish committing updates to notify all
     * observers.
     *
     * @param selfChange value
     */
    @SuppressWarnings("deprecation")
    protected void onChange(boolean selfChange) {
        synchronized (mSelfObserverLock) {
                            // mContentObservable.dispatchChange(selfChange);
            }
            if (mNotifyUri != null && selfChange) {
                mContentResolver.notifyChange(mNotifyUri);
            }
    }

    /**
     * Specifies a content URI to watch for changes.
     *
     * @param cr The content resolver from the caller's context.
     * @param notifyUri The URI to watch for changes. This can be a
     * specific row URI, or a base URI for a whole class of content.
     */
    @Override
    public void setAffectedByUris(Object cr, List <Uri> notifyUri) {
        if (cr instanceof DataAbilityHelper) {
            synchronized (mSelfObserverLock) {
                mNotifyUriList = notifyUri;
                for (Uri uri : notifyUri) {
                    mContentResolver = (DataAbilityHelper) cr;
                    if (mSelfObserver != null) {
                        mContentResolver.unregisterObserver(uri, mSelfObserver);
                    }
                    mSelfObserver = new SelfContentObserver(this);
                    mContentResolver.registerObserver(mNotifyUri, mSelfObserver);
                    mSelfObserverRegistered = true;
                }
            }
        }
    }

    @Override
    public List<Uri> getAffectedByUris() {
        return mNotifyUriList;
    }


    // @Override
    public boolean getWantsAllOnMoveCalls() {
        return false;
    }

    @Override
    public void setExtensions(PacMap extras) {
        mExtras = (extras == null) ? PacMap.EMPTY_PAC_MAP : extras;
    }

    @Override
    public PacMap getExtensions() {
        return mExtras;
    }

    // @Override
    public PacMap respond(PacMap extras) {
        return PacMap.EMPTY_PAC_MAP;
    }

    /**
     * This function throws CursorIndexOutOfBoundsException if the cursor position is out of bounds.
     * Subclass implementations of the get functions should call this before attempting to
     * retrieve data.
     *
     * @throws CursorIndexOutOfBoundsException
     */
    protected void checkPosition() {
        if (-1 == mPos || getRowCount() == mPos) {
            throw new CursorIndexOutOfBoundsException(mPos, getRowCount());
        }
    }

    @SuppressWarnings("FinalizeDoesntCallSuperFinalize")
    @Override
    protected void finalize() {
        if (mSelfObserver != null && mSelfObserverRegistered) {
            mContentResolver.unregisterObserver(mNotifyUri, mSelfObserver);
        }
        try {
            if (!mClosed) close();
        } catch(Exception ignored) { }
    }

    /**
     * Cursors use this class to track changes others make to their URI.
     */
    protected static class SelfContentObserver implements IDataAbilityObserver {
        WeakReference<AbstractCursor> mCursor;

        public SelfContentObserver(AbstractCursor cursor) {
            //super(null); // No super claSS
            mCursor = new WeakReference<>(cursor);
        }

        //@Override
        public boolean deliverSelfNotifications() {
            return false;
        }

        @Override
        public void onChange() {
            AbstractCursor cursor = mCursor.get();
            if (cursor != null) {
                cursor.onChange(false);
            }
        }
    }
}
